#!/usr/bin/env python
# -*- utf-8 -*-
__software__ = 'newzBrowser'
__version__ = '0.1.0'
__author__ = 'Franck Tabary <franck34@sourceforge.net>'
__copyright__ = 'Copyright (c) 2006 Franck TABARY'
__license__ = """
 Copyright (C) 2006 Franck TABARY <franck34@sourceforge.net>

 newzBrowser is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as
 published by the Free Software Foundation; either version 2 of
 the License, or (at your option) any later version.

 newzBrowser is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA."""

##############################################################################
# Import libraries
##############################################################################
import os.path
import sys
import cmd
import shlex

# Add local libs
sys.path.append(os.path.join('lib','newzBrowser'))
sys.path.append(os.path.join('lib','progressbar'))

# Import newzBrowser libs
try:
    import NbDb
    import NbNNTP as nntplib
except ImportError:
    raise ImportError, 'This program requires the newzBrowserDb extension'

# Import external libs (require for cli)
try:
    import progressbar
except ImportError:
    errstr='This program requires the progressbar extension for Python'
    errstr+='See http://cheeseshop.python.org/pypi/progressbar/'
    raise ImportError, errstr


class newzBrowserConsole(cmd.Cmd):

    # Some constante
    _db_dir='db'
    _db_dir_group='groups'
    _db_ext='.db'
    _db_file=__software__

    _user_home=os.path.expanduser('~')
    _user_home_app=os.path.join(_user_home,'.'+__software__)
    _db_path=os.path.join(_user_home_app,_db_dir)
    _db_path_group=os.path.join(_user_home_app,_db_dir,_db_dir_group)            

    NT_DB1=os.path.join(os.getcwd(),_db_path,'nntp-list.db')
    NT_DB2=os.path.join(os.getcwd(),_db_path,'nntp-account.db')
    NT_DB3=os.path.join(os.getcwd(),_db_path,'nntp-list-subscribed.db')

    prompt = __software__+'> '
    bar = '-'*79

    current_account=''
    debug_nntp=''

    ################################################################################
    # Initialisation and main loop
    ################################################################################

    def __init__(self):
        cmd.Cmd.__init__(self)

        # Create database directories if not exist
        if not os.path.exists(self._db_path_group):
            try:
                os.makedirs(self._db_path_group)
            except:
                raise "ERROR: Can't create databases directory %s" % self._db_path_group            
        
        self.NgListObj=NbDb.NgList(self.NT_DB1)
        self.NgAccountObj=NbDb.NgAccount(self.NT_DB2)
        self.NgListSubscribedObj=NbDb.NgListSubscribed(self.NT_DB3)
        
        self.NgList=self.NgListObj.select_all_key_name()
        self.NgListAutoComplete=self.NgListObj.select_all_for_autocomplete('ng_name')

        self.NgListSubscribed=self.NgListSubscribedObj.select_all_key_name()
        self.NgListSubscribedAutoComplete=self.NgListSubscribedObj.select_all_for_autocomplete('ng_name')
               
        self.NgAccount=self.NgAccountObj.select_all()
        
        if (len(self.NgAccount)==1):
            self.current_account=self.NgAccount[0]
            name=self.current_account[1];
            self.prompt = __software__+': '+name+'> '
          
    def run(self):

        print self.bar
        print __software__,'version',__version__
        print self.bar
        print 'Database:',len(self.NgList),'Newsgroup(s),',len(self.NgAccount),'Account(s)'

        while 1:
            try:
                self.cmdloop()
                break
            except KeyboardInterrupt:
                print "\n** Interrupt. Use 'quit' to exit **"

    ################################################################################
    # Internal tools (contextual help and autocompletion
    ################################################################################

    def print_listing(self, headers, data, sep=' ',decor=1):
        ldata = data
        if decor:
            ldata.insert (0, headers)
        print
        colw=[]
        ncols = len(ldata[0]) # assumes all rows are of equal length
        for cnum in xrange(0, ncols):
            mw = 0
            for cell in [d[cnum] for d in ldata]:
                if len(cell) > mw:
                    mw = len(cell)
            colw.append(mw)
        for rnum in xrange(0, len(ldata)):
            for cnum in xrange(0, ncols):
                if decor and rnum == 0:
                    sp = ('%%%ds' % len(sep)) % ' '  # No separator in header
                else:
                    sp = sep
                if cnum+1 == ncols: sp = '' # No separator after last column
                print ("%%-%ds%s" % (colw[cnum], sp)) % ldata[rnum][cnum],
            print
            if rnum == 0 and decor:
                print ''.join(['-' for x in xrange(0,(1+len(sep))*cnum+sum(colw))])
        print

    def print_doc(self,doc,decor=False):
        if not doc: return
        self.print_listing (['Command','Description'], doc, '  --', decor)

    def arg_tokenize (self, argstr):
        return shlex.split(argstr) or ['']

    def word_complete (self, text, words):
        return [a for a in words if a.startswith (text)]

    ################################################################################
    # Commands
    ################################################################################

    ## Quit / EOF
    _help_quit = [['quit', 'Exit the program']]

    def do_quit(self,line):
        print
        sys.exit()

    ## Help
    _help_help = [('help', 'Show documentation')]

    def do_help(self, line=None):
        arg = self.arg_tokenize(line)
        if arg[0]:
            try:
                doc = getattr(self, "_help_" + arg[0])
            except AttributeError:
                print "No documentation found for '%s'" % arg[0]
                return
            self.print_doc(doc)
        else:
            docs = (self._help_about + self._help_account + self._help_group +self._help_debug)
            print '%s Console %s' % (__software__,__version__)
            self.print_doc (docs)

    _help_about = [('about', 'Show information')]

    def do_about(self, line):
        print
        print 'About: %s Console %s' % (__software__,__version__)
        print '================================================================='

    ## Debug mode
    _help_debug = [('debug on','Enable display of NNTP commands'),
                   ('debug off','Disable display of NNTP commands')]

    def complete_debug (self, text, line, begidx, endixd):
        comp = ['on','off']
        return self.word_complete(text, comp)
        

    def do_debug(self,line):
        arg = self.arg_tokenize(line)
        try:
            if (arg[0] == 'on' or arg[0] == 'off'):
                self._do_debug(arg[0])
            else:
                self.do_help('debug')
        except:
            print 'Debug %s failed: ' % arg[0],e

    def _do_debug(self,line):
        if (line=='on'):
            self.debug_nntp='true'
        else:
            self.debug_nntp=''
            
    ## Account management 
    _help_account = [('account list', 'Show available account'),
                     ('account select <name>','Select account'),
                     ('account add <name> <host> <login> <password>', 'Add a new account'),
                     ('account edit <name>','Edit an account'),
                     ('account remove <name>', 'Remove account'),
                     ('account help','Ask for current account server capacilities')]

    def complete_account (self, text, line, begidx, endidx):
        comp = ['list','select','add','edit','remove','help']
        return self.word_complete(text, comp)
        
    def do_account(self,line):
        arg = self.arg_tokenize(line)
        #try:
        if arg[0]  == 'list':
            self._do_account_list()
        elif arg[0] == 'select' and len(arg)==2:
            name=arg[1]
            self._do_account_select(name)
        elif arg[0] == 'add' and len(arg)==5:
            name=arg[1]
            host=arg[2]
            login=arg[3]
            password=arg[4]
            self._do_account_add(name,host,login,password)
        elif arg[0] == 'edit' and len(arg)==2:
            name=arg[1]
            self._do_account_edit(name)
        elif arg[0] == 'remove' and len(arg)==2:
            name=arg[1]
            self._do_account_remove(name)
        elif arg[0] == 'help':
            self._do_account_help()
        else:    
            self.do_help ('account')
        #except Exception, e:
            #print 'Account %s failed:' % arg[0], e
         

    def _do_account_list(self):
        print self.bar
        if (len(self.NgAccount)>0):
            print '%4s %-20s %-25s %-5s %-20s' % ('','Name','Host','Port','Login')
            print self.bar
            for account in self.NgAccount:
                port=account[5]
                if (port==None): port='119'
                print '%3d. %-20s %-25s %-5s %-20s' % (account[0],account[1],account[2],port,account[3])
        else:
            print 'No account in database. You can add one using "account add"'

        print self.bar

    def _do_account_select(self,name):
        # TODO: check if name exist!
        self.current_account=self.NgAccountObj.select_once(name)
        self.prompt = __software__+': '+name+'> '
        print "Account %s is now the current account" % name
    

    def _do_account_add(self,name,host,login,password):
        # TODO: check if name doesn't already exist
        account_info={}
        account_info['acc_name']=name
        account_info['acc_host']=host
        account_info['acc_login']=login
        account_info['acc_password']=password
        self.NgAccountObj.add(account_info)
        print 'Account %s successfully added' % name
        self.NgAccount=self.NgAccountObj.select_all()
                
    def _do_account_edit(self,name):
        pass

    def _do_account_remove(self,name):
        #  TODO: check if name exist before remove it
        self.NgAccountObj.remove(name)
        print 'Account %s successfully removed' % name
        self.NgAccount=self.NgAccountObj.select_all()

    def _do_account_help(self):
        if (self.current_account==''):
            print 'Error: You must select an account first'
            return

        host=self.current_account[2]
        login=self.current_account[3]
        password=self.current_account[4]

        print 'Connecting',host,'...'
        try:
            ns = nntplib.NNTP(host,119,login,password,'true')
            if (self.debug_nntp):
                #ns.set_debuglevel(1)
                print ns.getwelcome()

        except Exception, err:
            print 'Could not connect to news server.'
            print err
            return

        help_lines=ns.help()
        for line in help_lines[1]:
            print line
        
    ## Newsgroup management 
    _help_group = [('group list', 'Show available group in database'),
                       ('group refreshlist','Refresh group list from current account'),
                       ('group search <string>','Display newwsgroup matching string'),
                       ('group searchsubject <group> <string>','Try to find subject containing string in newsgroup group'),
                       ('group info <group>','Show group informations'),
                       ('group subscribe <group>','Subscribe to the group'),
                       ('group update <group>','Update a subscribed group')]

    def complete_group (self, text, line, begidx, endidx):
        comp = ['list','info','subscribe','search','searchsubject','refreshlist','update']
        if (line.startswith('group info') or line.startswith('group subscribe')):
            comp = self.NgListAutoComplete
        if (line.startswith('group update') or line.startswith('group searchsubject')):
            comp = self.NgListSubscribedAutoComplete
        return self.word_complete(text, comp)
        
    def do_group(self,line):
        arg = self.arg_tokenize(line)
        #try:
        if arg[0]  == 'list':
            self._do_group_list()
        elif arg[0] == 'refreshlist':
            self._do_group_refreshlist()
        elif arg[0] == 'search':
            str = arg[1]
            self._do_group_search(str)
        elif arg[0] == 'searchsubject' and len(arg)==3:
            name=arg[1]
            search=arg[2]
            self._do_group_searchsubject(name,search)
        elif arg[0] == 'info':
            name = arg[1]
            self._do_group_info(name)
        elif arg[0] == 'subscribe':
            name = arg[1]
            self._do_group_subscribe(name)
        elif arg[0] == 'update' and len(arg)==2:
            name = arg[1]
            self._do_group_update(name)
        elif arg[0] == 'update' and len(arg)==3:
            name = arg[1]
            max = arg[2]
            self._do_group_update(name,max)
        else:    
            self.do_help ('group')
        #except Exception, e:
            #print 'Account %s failed:' % arg[0], e

    def _do_group_list(self):
        pass

    def _do_group_info(self,name):

        host=self.current_account[2]
        login=self.current_account[3]
        password=self.current_account[4]

        print 'Connecting',host,'...'
        try:
            ns = nntplib.NNTP(host,119,login,password,'true')
            if (self.debug_nntp):
                ns.set_debuglevel(1)
                print ns.getwelcome()
        except Exception, err:
            print 'Could not connect to news server.'
            print err  
            return          

        try:
            resp, count, first, last, name = ns.group(name)
        except ns.NNTPError:
            print 'Error'
            return 
        diff=int(last)-int(first)
        print 'Information for group %s: %s messages (start: %s - end: %s = %s)' % (name,count,first,last,diff)

    def _do_group_subscribe(self,name):
        self.NgListSubscribedObj.add(name,'0')
        self.NgListSubscribedObj.commit()        
        self.NgListSubscribed=self.NgListSubscribedObj.select_all_key_name()
        self.NgListSubscribedAutoComplete=self.NgListSubscribedObj.select_all_for_autocomplete('ng_name')
        print 'You have subcribed to %s' % name

    def _do_group_searchsubject(self,name,search):
        if (self.current_account==''):
            print 'Error: You must select an account first'
            return

        host=self.current_account[2]
        login=self.current_account[3]
        password=self.current_account[4]    

        print 'Connecting',host,'...'
        try:
            ns = nntplib.NNTP(host,119,login,password,'true')
            if (self.debug_nntp):    
                ns.set_debuglevel(1)
                print ns.getwelcome()
        except Exception, err:
            print 'Could not connect to news server.'
            print err
            return   

        resp, count, first, last, name =ns.group(name)
        diff = int(last)-int(first)
        print 'Information for group %s: %s messages (start: %s - end: %s = %s)' % (name,count,first,last,diff)
        try:
            resp, subs = ns.xpat('subject', first + '-' + last ,search)
            for id, sub in subs: print id, sub
        except nntplib.NNTPError:
            print 'Error'
            return


    def _do_group_update(self,name, maxuser = 0):
        if (self.current_account==''):
            print 'Error: You must select an account first'
            return

        host=self.current_account[2]
        login=self.current_account[3]
        password=self.current_account[4]    

        print 'Connecting',host,'...'
        try:
            ns = nntplib.NNTP(host,119,login,password,'true')
            if (self.debug_nntp):    
                ns.set_debuglevel(1)
                print ns.getwelcome()
                                            
        except Exception, err:
            print 'Could not connect to news server.'
            print err
            return

        resp, count, first, last, name =ns.group(name)
        diff = int(last)-int(first)
        print 'Information : Group %s: %s messages (start: %s - end: %s = %s)' % (name,count,first,last,diff)
        max=2000
        if (maxuser == 0):
            if (count>max):
                print 'Warning: this group contain more than 500 articles'
                entry=raw_input('How many last articles do you want to download [all,x,default x='+str(max)+']: ')
                if entry=='all':
                    start=first
                elif entry=='':
                    start=str(int(last)-max)
                else:
                    start=str(int(last)-int(entry))
        else:
            start=str(int(last)-int(maxuser))
        if int(start)<int(first): start=first
       
        print "Getting format of overview .."
        format=ns.overview_format()

        print "Download headers ... (XOVER ",start+'-'+last+')'
        resp, subs = ns.xover(start + '-' + last)
       
        NgGroupObj=NbDb.NgGroup(self.NT_DB_PATH+'/groups/'+name+'.db',format[1])
        NgGroupObj.update(subs)
        
    def _do_group_search(self,str):
        pass        

    def _do_group_refreshlist(self):

        if (self.current_account==''):
            print 'Error: You must select an account first'
            return

        host=self.current_account[2]
        login=self.current_account[3]
        password=self.current_account[4]

        ng_list={}
        print 'Connecting',host,'...'
        try:
            ns = nntplib.NNTP(host,119,login,password,'true')
            if (self.debug_nntp):    
                ns.set_debuglevel(1)
                print ns.getwelcome()
            
        except Exception, err:
            print 'Could not connect to news server.'
            print err
            return

        print 'Getting group list, please wait ...'
        try:
            ng_list=ns.list()
        except:
            print 'Could not get the list'
            return

        print 'Newsgroup server return',len(ng_list[1]),'newgroups available'

        widgets = [progressbar.Bar('#'), ' ', progressbar.Percentage(),' ', progressbar.ETA()]
        pbar = progressbar.ProgressBar(widgets=widgets, maxval=len(ng_list[1])).start()

        i = 0
        nb_group_updated = 0
        nb_group_inserted = 0
        total_nb_inserted_message = 0
        total_nb_deleted_message = 0

        req='UPDATE ng_list SET last_nb_msg_updated=0'
        self.NgListObj.cur.execute(req)
        self.NgListObj.con.commit()

        for name,high,low,flag in ng_list[1]:
            if (not self.NgList.has_key(name)):
                nb_inserted_message=int(high)-int(low)
                total_nb_inserted_message += nb_inserted_message
                nb_group_inserted += 1
                self.NgListObj.add(name,high,low,flag,nb_inserted_message)
            else:
                idng=self.NgList[name]
                current_high=idng[2]
                current_low=idng[3]
                nb_inserted_message=int(high)-int(current_high)
                nb_deleted_message=int(low)-int(current_low)
                #if (nb_inserted_message>100):
                    #print name+': inserted msg = '+str(nb_inserted_message)
                    #print name+': high='+high+' '+str(int(high))
                    #print name+': previous high='+current_high+' '+str(int(current_high))
                total_nb_inserted_message += nb_inserted_message
                total_nb_deleted_message += nb_deleted_message
                if (current_high!=high or current_low!=low):
                    nb_group_updated += 1
                    self.NgListObj.edit(name,high,low,str(nb_inserted_message))

                
            i += 1
            pbar.update(i)

        if (nb_group_updated>0 or nb_group_inserted>0):
            self.NgListObj.commit()
            self.NgList=self.NgListObj.select_all_key_name()
            self.NgListAutoComplete=self.NgListObj.select_all_for_autocomplete('ng_name')

        if (nb_group_updated):
            print str(nb_group_updated),'group updated'
            print str(total_nb_inserted_message),'messages added'
            print str(total_nb_deleted_message),'messages removed'

        if (nb_group_inserted):
            print str(nb_group_inserted),'new group'
            print 'Now '+str(len(self.NgList))+' group in database',total_nb_inserted_message,'new messages' 

        if (nb_group_inserted==0 and nb_group_updated==0):
            print 'No changes'
        else:
            req="SELECT ng_name,last_nb_msg_updated FROM ng_list ORDER BY last_nb_msg_updated DESC LIMIT 10"
            self.NgListObj.cur.execute(req)
            for row in self.NgListObj.cur.fetchall():
                print row[0],':',row[1],'new messages' 
        return

###############################################
## Main
###############################################
def main():
    con = newzBrowserConsole()
    if len (sys.argv) > 1:
        if sys.argv[1] in ['-h','--help','help']:
            con.onecmd ("help")
        elif sys.argv[1] in ['-v','--version','version','about']:
            con.onecmd ("version")
    else:
        con.run()

if __name__ == '__main__':
    main()

